package org.erepublik.company;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.JAXBException;

import org.erepublik.company.generated.CompanyT;
import org.erepublik.company.generated.EmployeeT;
import org.erepublik.company.product.EProductAttribute;
import org.erepublik.company.product.Food;
import org.erepublik.company.product.House;
import org.erepublik.company.product.ProductDesign;
import org.erepublik.company.product.Raw;
import org.erepublik.company.product.Ticket;
import org.erepublik.company.product.Weapons;
import org.erepublik.company.searchCriteria.CompanySimulatorFetch;
import org.erepublik.company.searchCriteria.ERawRegion;
import org.erepublik.company.service.IProductsService;
import org.erepublik.company.service.IVatService;
import org.erepublik.company.service.JSONProductsService;
import org.erepublik.company.service.RawRegionsService;
import org.erepublik.company.service.XmlVatService;
import org.erepublik.company.utils.Utils;
import org.erepublik.company.utils.WebpageReader;
import org.erepublik.company.xml.XMLParsersers;
import org.json.JSONException;
import org.json.JSONObject;

public class CompanyCalculator {

    private Company company;
    private EProductType companyType;
    private int quality;
    private ArrayList<Worker> workers = new ArrayList<Worker>();

    private double dailyRevenue;
    private double dailyProduction;
    private double pricePerUnitWithoutTaxes;
    private boolean maxWellness = false;
    private boolean readSalaries = false;
    private ERawRegion rawRegion = ERawRegion.HIGH;

    public ERawRegion getRawRegion() {
        return rawRegion;
    }

    public int getVat() {
        return vat;
    }

    public void setVat(int vat) {
        this.vat = vat;
    }

    public double getPricePerUnitWithTaxes() {
        return pricePerUnitWithTaxes;
    }

    public void setPricePerUnitWithTaxes(double pricePerUnitWithTaxes) {
        this.pricePerUnitWithTaxes = pricePerUnitWithTaxes;
    }

    private int vat = 0;
    private double pricePerUnitWithTaxes;

    private int rawUsed;
    private double rawCost;
    private double totalRawPrice;

    private double dailySalaries;

    private IProductsService productService;
    private IVatService vatService;

    public IProductsService getProductService() {
        return productService;
    }

    public void setProductService(IProductsService productService) {
        this.productService = productService;
    }

    public CompanyCalculator(int id, IProductsService productService, IVatService vatService, boolean maxWellness, boolean readSalaries, RawRegionsService rawRegionService) {
        this.maxWellness = maxWellness;
        this.productService = productService;
        this.readSalaries = readSalaries;
        this.vatService = vatService;

        CompanyT companyXML = null;
        try {
            companyXML = XMLParsersers.parseCompany(WebpageReader.getStreamToIrl("http://api.erepublik.com/v2/feeds/companies/" + id));
        } catch (JAXBException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        company = new Company();
        company.setId(id);
        company.setName(companyXML.getName());
        company.setCountry(ECountry.getCountryByName(companyXML.getCountry().getName()));
        companyType = EProductType.getProductTypeByName(companyXML.getIndustry().getName());
        if (companyType.isRaw())
            rawRegion = rawRegionService.getRawRegion(companyType, companyXML.getRegion().getName());
        
        quality = companyXML.getCustomizationLevel();
        List<EmployeeT> workers = companyXML.getEmployees().getEmployee();
        for (EmployeeT employeeT : workers) {
            this.workers.add(getWorkerFromJSON(employeeT.getId()));
        }

        setProductCost();
        if (this.readSalaries)
            setSalariesFromJobMarket();

    }

    private void setProductCost() {
        if (!companyType.isRaw()) {
            Raw[] raws = productService.getRaws(company.getCountry(), getRawType(companyType));
            if (raws.length == 0)
                rawCost = 0.09;
            else
                rawCost = raws[0].getPrice();

            try {
                vat = (int) (vatService.getVat().get(company.getCountry()).getVat(companyType) * 100.0d);
            } catch (Exception e) {
            }

            pricePerUnitWithTaxes = getProductPrice();
            pricePerUnitWithoutTaxes = Utils.round(pricePerUnitWithTaxes * (100.0d / (100.0 + vat)), 2);
        } else {
            Raw[] raws = productService.getRaws(company.getCountry(), companyType);
            if (raws.length == 0)
                pricePerUnitWithoutTaxes = 999;
            pricePerUnitWithoutTaxes = raws[0].getPrice();
            pricePerUnitWithTaxes = pricePerUnitWithoutTaxes;
        }
    }

    public CompanyCalculator(IProductsService productService, XmlVatService vatService, CompanySimulatorFetch criteria) {
        this.productService = productService;
        this.vatService = vatService;
        readSalaries = true;
        company = new Company();
        company.setId(1);
        company.setName("Simulation company");
        company.setCountry(criteria.getCountry());
        companyType = criteria.getProductType().getProductType();
        if (companyType.isRaw()) {
            quality = 1;
            rawRegion = criteria.getRawRegion();
        }
        else 
            quality = criteria.getQuality().getQuality();
        setProductCost();
        createWirtualWorkers(criteria.isBestWorkers(), criteria.getWorkerWellness().getValue());
    }

    private void createWirtualWorkers(boolean bestWorkers, int wellness) {
    
        for (int i = 1; i <= 10; i++) {
            Worker worker = new Worker();
            worker.setId(1);
            worker.setName("Virtual worker");
            worker.setSkill(i);
            worker.setWellness(wellness);
            workers.add(worker);
            if (companyType == EProductType.HOUSE) {
                workers.add(worker.clone());
            }
        }

        setSalariesFromJobMarket();
        calculateProfit();
        
        if (bestWorkers) {
        double maxProfit=-99999;
            Worker maxProfitWorker = null;
            for (int i=0; i < workers.size(); i++) {
                if (workers.get( i ).getProfit()>maxProfit) {
                    maxProfitWorker = workers.get( i );
                    maxProfit = workers.get( i ).getProfit();
                    
                }
            }
            
            workers = new ArrayList<Worker>();
            for (int i = 1; i <= 10; i++) {
                workers.add(maxProfitWorker);
                if (companyType == EProductType.HOUSE) {
                    workers.add(maxProfitWorker.clone());
                }
            }
        }
    }

    private double getProductPrice() {
        if (companyType == EProductType.ARTILLERY || companyType == EProductType.HELICOPTER || companyType == EProductType.TANK
                || companyType == EProductType.RIFLE) {
            Weapons[] weapons = productService.getWeapons(company.getCountry(), getWeaponDesign(quality), companyType);
            if (weapons.length == 0)
                return 999.9;
            else
                return weapons[0].getPrice();
        }

        if (companyType == EProductType.HOUSE) {
            House[] houses = productService.getHouses(company.getCountry());
            houses = qualityFilter(houses, quality);

            if (houses.length == 0)
                return 999.9;
            else
                return houses[0].getPrice();
        }

        if (companyType == EProductType.FOOD) {
            Food[] food = productService.getFood(company.getCountry());
            food = qualityFilter(food, quality);

            if (food.length == 0)
                return 999.9;
            else
                return food[0].getPrice();
        }

        if (companyType == EProductType.TICKET) {
            int distance = 5;
            if (quality == 1)
                distance = 1;
            if (quality == 2)
                distance = 3;
            Ticket[] food = productService.getTickets(company.getCountry(), distance);
            food = qualityFilter(food, quality);

            if (food.length == 0)
                return 999.9;
            else
                return food[0].getPrice();
        }

        throw new IllegalStateException("brak obslugi takiego typu produktu");
    }

    private House[] qualityFilter(House[] products, int filter) {
        ArrayList<House> houses = new ArrayList<House>();
        for (House house : products) {
            if (house.getQuality() == filter)
                houses.add(house);
        }
        return houses.toArray(new House[0]);
    }

    private Food[] qualityFilter(Food[] products, int filter) {
        ArrayList<Food> houses = new ArrayList<Food>();
        for (Food house : products) {
            if (house.getQuality() == filter)
                houses.add(house);
        }
        return houses.toArray(new Food[0]);
    }

    private Ticket[] qualityFilter(Ticket[] products, int filter) {
        ArrayList<Ticket> houses = new ArrayList<Ticket>();
        for (Ticket house : products) {
            if (house.getQuality() == filter)
                houses.add(house);
        }
        return houses.toArray(new Ticket[0]);
    }

    private ProductDesign getWeaponDesign(int quality) {
        ProductDesign design = new ProductDesign(EProductType.ARTILLERY);
        design.setAttributeValue(EProductAttribute.DAMAGE, 20 * quality);
        design.setAttributeValue(EProductAttribute.DURABILITY, 10 * quality);
        return design;
    }

    private EProductType getRawType(EProductType productType) {
        if (productType == EProductType.ARTILLERY)
            return EProductType.TITANIUM;
        if (productType == EProductType.DEFENSE_SYSTEM)
            return EProductType.STONE;
        if (productType == EProductType.FOOD)
            return EProductType.GRAIN;
        if (productType == EProductType.HELICOPTER)
            return EProductType.TITANIUM;
        if (productType == EProductType.HOUSE)
            return EProductType.STONE;
        if (productType == EProductType.RIFLE)
            return EProductType.IRON;
        if (productType == EProductType.TANK)
            return EProductType.IRON;
        if (productType == EProductType.TICKET)
            return EProductType.OIL;

        return null;
    }

    private Worker getWorkerFromJSON(int id) {
        Worker worker = new Worker();
        worker.setId(id);
        String jsonString = null;
        try {
            jsonString = WebpageReader.getPage("http://api.erepublik.com/v2/feeds/citizens/" + id + ".json");
        } catch (IOException e) {
            e.printStackTrace();
        }
        JSONObject object = null;
        try {
            object = new JSONObject(jsonString);
            worker.setName((String) object.get("name"));
            if (maxWellness)
                worker.setWellness(100);
            else
                worker.setWellness(((Double) object.get("wellness")).intValue());
            worker.setSkill(transferToSill((Integer) object.get("work_skill_points")));
        } catch (JSONException e) {
            e.printStackTrace();
        }

        return worker;
    }

    private int transferToSill(int workSkillPoints) {
        if (workSkillPoints < 20)
            return 1;
        if (workSkillPoints < 100)
            return 2;
        if (workSkillPoints < 500)
            return 3;
        if (workSkillPoints < 2000)
            return 4;
        if (workSkillPoints < 5000)
            return 5;
        if (workSkillPoints < 10000)
            return 6;
        if (workSkillPoints < 20000)
            return 7;
        if (workSkillPoints < 40000)
            return 8;
        if (workSkillPoints < 80000)
            return 9;
        if (workSkillPoints < 160000)
            return 10;
        if (workSkillPoints < 320000)
            return 11;
        if (workSkillPoints < 640000)
            return 12;
        return 13;
    }

    private double dailyProfit;

    public double getDailySalaries() {
        return dailySalaries;
    }

    public void setDailySalaries(double dailySalaries) {
        this.dailySalaries = dailySalaries;
    }

    public Company getCompany() {
        return company;
    }

    public void setCompany(Company company) {
        this.company = company;
    }

    public EProductType getCompanyType() {
        return companyType;
    }

    public void setCompanyType(EProductType companyType) {
        this.companyType = companyType;
    }

    public int getQuality() {
        return quality;
    }

    public void setQuality(int quality) {
        this.quality = quality;
    }

    public ArrayList<Worker> getWorkers() {
        return workers;
    }

    public void setWorkers(ArrayList<Worker> workers) {
        this.workers = workers;
    }

    public double getDailyRevenue() {
        return dailyRevenue;
    }

    public void setDailyRevenue(double dailyRevenue) {
        this.dailyRevenue = dailyRevenue;
    }

    public double getDailyProduction() {
        return dailyProduction;
    }

    public void setDailyProduction(double dailyProduction) {
        this.dailyProduction = dailyProduction;
    }

    public double getPricePerUnitWithoutTaxes() {
        return pricePerUnitWithoutTaxes;
    }

    public void setPricePerUnitWithoutTaxes(double pricePerUnitWithoutTaxes) {
        this.pricePerUnitWithoutTaxes = pricePerUnitWithoutTaxes;
    }

    public int getRawUsed() {
        return rawUsed;
    }

    public void setRawUsed(int rawUsed) {
        this.rawUsed = rawUsed;
    }

    public double getRawCost() {
        return rawCost;
    }

    public void setRawCost(double rawCost) {
        this.rawCost = rawCost;
    }

    public double getTotalRawPrice() {
        return totalRawPrice;
    }

    public void setTotalRawPrice(double totalRawPrice) {
        this.totalRawPrice = totalRawPrice;
    }

    public double getDailyProfit() {
        return dailyProfit;
    }

    public void setDailyProfit(double dailyProfit) {
        this.dailyProfit = dailyProfit;
    }

    public void calculateProfit() {
        int production = 0;
        double totalSalaries = 0;

        for (int i = 0; i < workers.size(); i++) {
            production += getWorkerProduction(workers.get(i));
            totalSalaries += workers.get(i).getSalary();
        }

        if (!companyType.isRaw()) {
            setRawUsed(production);
            setTotalRawPrice(Utils.round(production * getRawCost(), 2));
        }

        setDailyProduction(Utils.round(production / (double) (getCompanyType().getRawRequired() * getQuality()), 2));
        setDailyRevenue(Utils.round(getDailyProduction() * getPricePerUnitWithoutTaxes(), 2));
        setDailySalaries(Utils.round(totalSalaries, 2));
        setDailyProfit(Utils.round(getDailyRevenue() - getDailySalaries() - getTotalRawPrice(), 2));

        for (Worker worker : workers) {
            worker.setProfit(-worker.getSalary() - getRawCost() * worker.getProductivity() + getDailyRevenue()
                    * (worker.getProductivity() / (double) production));
            worker.setProfit(Utils.round(worker.getProfit(), 2));
        }
    }

    private void setSalariesFromJobMarket() {
        Job[] jobs = productService.getJobs(company.getCountry(), 12);
        for (int i = 0; i < workers.size(); i++) {
            workers.get(i).setSalary(getSalary(jobs, workers.get(i).getSkill()));
        }
    }

    private double getSalary(Job[] jobs, int skill) {
        for (Job job : jobs) {
            if (skill == job.getMinimalSkill())
                return job.getSalaryInLocalBeforeTax();
        }
        return 1.0d;
    }

    private int getWorkerProduction(Worker worker) {

        double workerNumberMultiplier = getWorkerNumberMultiplier(workers.size());
        double wellnessMultiplier = getWellnessMultiplier(worker.getWellness());
        double rawMultiplier = getRawMultiplier();
        double skillLevel = getSkillMultiplier(worker.getSkill());
        double boosterMultiplier = getBooserMultiplier();

        double productivity = workerNumberMultiplier * wellnessMultiplier * rawMultiplier * skillLevel * boosterMultiplier * 8;
        worker.setProductivity((int) productivity);
        worker.setUnitsProduced(Utils.round(productivity / (getCompanyType().getRawRequired() * getQuality()), 2));
        return (int) productivity;
    }

    private double getBooserMultiplier() {
        return 1.1;
    }

    private double getSkillMultiplier(int skill) {
        return 3.6 + skill * 0.4;
    }

    private double getRawMultiplier() {
        if (rawRegion == ERawRegion.HIGH)
            return 1;
        if (rawRegion == ERawRegion.MEDIUM)
            return 0.5;
        return 0.01;
    }

    private double getWellnessMultiplier(int wellness) {
        return 1 + 2 * wellness / 100.0;
    }

    private double getWorkerNumberMultiplier(int workersNumber) {
        int recommendedWorkers = 10;
        if (companyType == EProductType.DEFENSE_SYSTEM || companyType == EProductType.HOUSE)
            recommendedWorkers = 20;
        double multiplier = 0;
        if (workersNumber <= recommendedWorkers)
            multiplier = 1 + (workersNumber / (double) recommendedWorkers);
        if (workersNumber > recommendedWorkers)
            multiplier = 3 - (workersNumber / (double) recommendedWorkers);

        if (multiplier < 1)
            multiplier = 1;

        return multiplier;
    }

    public static void main(String[] args) {
        CompanyCalculator company = null;
        try {
            company = new CompanyCalculator(203604, new JSONProductsService(), new XmlVatService(), true, true, new RawRegionsService());
        } catch (IOException e) {
            e.printStackTrace();
        } catch (JSONException e) {

            e.printStackTrace();
        }
        company.calculateProfit();

        System.out.println("daily sales: " + company.getDailyRevenue());
        System.out.println("raw cost: " + company.getRawCost());
        System.out.println("product price: " + company.getPricePerUnitWithoutTaxes());
        System.out.println("daily productio: " + company.getDailyProduction());
        System.out.println("daily profit: " + company.getDailyProfit());
        System.out.println("raw usage: " + company.getRawUsed());
        System.out.println("total raw cost: " + company.getTotalRawPrice());
        ArrayList<Worker> workers = company.getWorkers();
        for (int i = 0; i < workers.size(); i++) {
            System.out.println("worker  " + i + " " + workers.get(i).getName() + " " + workers.get(i).getSkill() + " "
                    + workers.get(i).getWellness() + " " + workers.get(i).getProductivity() + " " + workers.get(i).getProfit());
        }
    }

}
